package br.com.officium.sessionbean;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;

/**
 *
 * @author Ivo
 */
public abstract class AbstractSessionBean<T> {

    private Class<T> entityClass;

    public AbstractSessionBean(Class<T> entityClass) {
        this.entityClass = entityClass;
    }

    protected abstract EntityManager getEntityManager();

    public void create(T entity) {
        getEntityManager().persist(entity);
    }

    public void edit(T entity) {
        getEntityManager().merge(entity);
    }

    public void remove(T entity) {
        getEntityManager().remove(getEntityManager().merge(entity));
    }

    public T find(Object id) {
        return getEntityManager().find(entityClass, id);
    }

    public List<T> findAll() {
        CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
        cq.select(cq.from(entityClass));

        return getEntityManager().createQuery(cq).getResultList();
    }

    public List<T> findRange(int[] range) {
        CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
        cq.select(cq.from(entityClass));
        Query q = getEntityManager().createQuery(cq);
        q.setMaxResults(range[1] - range[0]);
        q.setFirstResult(range[0]);

        return q.getResultList();
    }

    public int count() {
        CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
        Root<T> rt = cq.from(entityClass);
        cq.select(getEntityManager().getCriteriaBuilder().count(rt));
        Query q = getEntityManager().createQuery(cq);

        return ((Long) q.getSingleResult()).intValue();
    }

    public List<T> findByParameter(T entity) {
        StringBuilder sb = new StringBuilder("select o from ");
        sb.append(entity.getClass().getSimpleName());
        Class cls = entity.getClass();
        Properties props = new Properties();
        boolean isAnd = false;
        do {
            try {
                Field[] fieldlist = cls.getDeclaredFields();
                for (int i = 0; i < fieldlist.length; i++) {
                    Field field = fieldlist[i];
                    field.setAccessible(true);
                    Collection coll = null;

                    if ((!field.getName().equals("serialVersionUID"))
                            && (field.get(entity) != null)) {
                        if (field.get(entity) instanceof Collection) {
                            coll = (Collection) field.get(entity);
                        }
                        if ((coll == null) || (!coll.isEmpty())) {
                            sb.append(isAnd ? " and " : " where ");
                            isAnd = true;
                            sb.append(" o.").append(field.getName()).append("= :").append(field.getName());
                            props.put(field.getName(), field.get(entity));
                        }
                    }
                }
                cls = cls.getSuperclass();
            } catch (Exception e) {
                System.err.println(e);
            }
        } while (!cls.getName().equals("java.lang.Object"));
        Query query = getEntityManager().createQuery(sb.toString());
        for (Enumeration en = props.propertyNames(); en.hasMoreElements();) {
            String key = (String) en.nextElement();
            query.setParameter(key, props.get(key));
        }
        try {
            return query.getResultList();
        } catch (NoResultException e) {
            return null;
        }
    }

    protected T findOneResult(String namedQuery, Map<String, Object> parameters) {
        
        T result = null;

        try {
            Query query = getEntityManager().createNamedQuery(namedQuery);

            // Method that will populate parameters if they are passed not null and empty
            if (parameters != null && !parameters.isEmpty()) {
                populateQueryParameters(query, parameters);
            }

            result = (T) query.getSingleResult();

        } catch (Exception e) {
            return null;
        }

        return result;
    }

    private void populateQueryParameters(Query query, Map<String, Object> parameters) {

        for (Entry<String, Object> entry : parameters.entrySet()) {
            query.setParameter(entry.getKey(), entry.getValue());
        }
    }

}
